#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//3.3 void* 指针
//在指针类型中有⼀种特殊的类型是 void* 类型的，可以理解为⽆具体类型的指针（或者叫泛型指
//针），这种类型的指针可以⽤来接受任意类型地址。但是也有局限性， void* 类型的指针不能直接进
//⾏指针的 + -整数和解引⽤的运算。
//int main()
//{
	//int a = 10;
	//void* p = &a;
	//*p = 5;//p无具体类型，解引用时不知道访问几个字节(错误写法)
	//p=p+1;//向后走几个字节不确定(错误)
	//⼀般 void* 类型的指针是使⽤在函数参数的部分，⽤来接收不同类型数据的地址
//	return 0;
//}

//4. const 修饰指针
//4.1 const修饰变量	
//int main()
//{
//	const int a = 20;//a有了常属性，不能被修改
//	//虽然a不能被修改，但是a本质上还是变量
//	int* p = &a;
//	*p = 0;//使⽤a的地址，去修改a就能做到了，虽然这样做是在打破const的限制，这是不合理的，所以应该让p拿到a的地址也不能修改a，那接下来怎么做呢？
//	printf("a=%d", a);
//	return 0;
//}

//4.2 const修饰指针变量
//⼀般来讲const修饰指针变量，可以放在* 的左边，也可以放在* 的右边，意义是不⼀样的。
//int main()
//{
	//int a = 10;
	//int b = 20;
	
	//1.
	//int* const p=&a;//const修饰p,p不可被修改(地址不可被修改)
	//p = &b;//p不可修改
	//*p = 0;//p指向的值可以修改因为const修饰的仅仅只是p，而不是*p
	
	//2.
	//int const* p = &a;//const修饰的是*p，不可修改p指向的值
	//p = &b;//yes
	//*p = 0;//no
	
	//int a = 10;
	//int b = 20;
	//const int* p = &a;//这种的限制的是整体*p,属于第2种类型
	//*p = 0;//no
	//p = &b;//ok

	//int const* const p;//p和*p都不可以修改
//	return 0;
//}


//int main()
//{
	//5. 指针运算
	//5.1 指针+- 整数
	//type* p;
	//p+1 ---> 跳过1*sizeof(type)个字节
	//p+n ---> 跳过n*sizeof(type)个字节(减同理)
	//应用:打印数组的内容:
	//int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//int* p = arr;
	//for (int i = 0; i < 10; i++)
	//{
	//	printf("%d ", *(p + i));
	//}


	//5.2 指针-指针
	//指针1+整数=指针2
	//那么：指针2-指针1=整数（这里的整数指的是两个指针之间的元素个数）
	//指针+指针（无意义）
	//int arr[10] = { 0 };
	//printf("%zd\n", &arr[9] - &arr[0]);
	//printf("%zd\n", &arr[0] - &arr[9]);
	//指针-指针 能计算的前提条件是:两个指针指向的是同一块空间


	//5.3 指针的关系运算
	//int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//int* p = arr;
	//int sz = sizeof(arr) / sizeof(arr[0]);
	//while (p < arr + sz)
	//{
	//	printf("%d ", *p);
	//	p++;
	//}
//	return 0;
//}


//6. 野指针
//概念： 野指针就是指针指向的位置是不可知的（随机的、不正确的、没有明确限制的）
//6.1 野指针成因
//	1. 指针未初始化
//int* p;//指向的是随机值，非法访问
//*p = 10;

//2. 指针越界访问
//指针指向数组以外的位置

//3. 指针指向的空间释放:
//int* tset()
//{
//	int a = 10;
//	return &a;
//}
//int main()
//{
//	int* p = test();
//	printf("%d", *p);//当调用完函数再回到主函数时，形参a的内存被释放，p得到的就是野指针
//	return 0;
//}

//6.2 如何规避野指针
//6.2.1 指针初始化
//如果明确知道指针指向哪⾥就直接赋值地址，如果不知道指针应该指向哪⾥，可以给指针赋值NULL
//NULL是C语⾔中定义的⼀个标识符常量，值是0，0也是地址，这个地址是⽆法使⽤的，读写该地址会报错
//6.2.2 ⼩⼼指针越界
//6.2.3 指针变量不再使⽤时，及时置NULL，指针使⽤之前检查有效性
//6.2.4 避免返回局部变量的地址



//7. assert 断⾔
//assert.h 头⽂件定义了宏 assert() ，⽤于在运⾏时确保程序符合指定条件，如果不符合，就报
//错终⽌运⾏。这个宏常常被称为“断⾔”。
//使⽤ assert() 有⼏个好处：它不仅能⾃动标识⽂件和
//出问题的⾏号，还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问
//题，不需要再做断⾔，就在 #include <assert.h> 语句的前⾯，定义⼀个宏 NDEBUG 。
//#define NDEBUG
//#include<assert.h>
//int main()
//{
//	int* p = NULL;
//	assert(p != NULL);
//	return 0;
//}


//8. 指针的使⽤和传址调⽤
//8.1 strlen的模拟实现
//size_t my_strlen(char const* p)
//{
//	size_t count = 0;
//	while (*p != '\0')
//	{
//		count++;
//		p++;
//	}
//	return count;
//}
//int main()
//{
//	char arr[] = "abcdef";
//	char* p = arr;
//	size_t len = my_strlen(p);
//	printf("%zd", len);
//	return 0;
//}


//8.2 传值调⽤和传址调⽤
//传址调用:
//void spaw(int* pa, int* pb)
//{
//	int temp = *pa;
//	*pa = *pb;
//	*pb = temp;
//}
//int main()
//{
//	int a = 10;
//	int b = 20;
//	int* pa = &a;
//	int* pb = &b;
//	printf("交换前:a=%d b=%d\n", a,b);
//	spaw(pa,pb);
//	printf("交换后:a=%d b=%d\n", a, b);
//	return 0;
//}
//传址调⽤，可以让函数和主调函数之间建⽴真正的联系，在函数内部可以修改主调函数中的变量；所
//以未来函数中只是需要主调函数中的变量值来实现计算，就可以采⽤传值调⽤。如果函数内部要修改
//主调函数中的变量的值，就需要传址调⽤。